/*
 * ==========================================================================*\
 * | $Id$
 * |*-------------------------------------------------------------------------*|
 * | Copyright (C) 2009 Virginia Tech | | This file is part of CloudSpace. | |
 * CloudSpace is free software; you can redistribute it and/or modify | it under
 * the terms of the GNU General Public License as published | by the Free
 * Software Foundation; either version 3 of the License, or | (at your option)
 * any later version. | | CloudSpace is distributed in the hope that it will be
 * useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of |
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General
 * Public License for more details. | | You should have received a copy of the
 * GNU General Public License | along with CloudSpace; if not, see
 * <http://www.gnu.org/licenses/>.
 * \*==========================================================================
 */

package cloudspace.security;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.springframework.security.core.GrantedAuthority;

import cloudspace.config.CloudSpaceConfiguration;
import cloudspace.security.permissions.PermissionsManager;
import cloudspace.security.permissions.UserPermissionToken;
import cloudspace.vm.filesystem.CSPath;

import com.Ostermiller.util.MD5;


// -------------------------------------------------------------------------
/**
 * A singleton that manages the list of CloudSpace users.
 * 
 * @author Tony Allevato
 * @author Last changed by $Author$
 * @version $Revision$, $Date$
 */
public class UserRegistry
{
    // ~ Static/instance variables .............................................
    public static final String AUTHENTICATOR_LOCAL = "local";

    public static final String AUTHENTICATOR_LDAP = "ldap";

    private static UserRegistry instance;

    private static final String USER_CONFIG_FILE = "conf/user.conf";

    private Map<String, CloudSpaceUser> userMap;

    private static final Logger log = Logger.getLogger( UserRegistry.class );

    private long lastChecked = 0L;


    // ~ Constructors ..........................................................

    // ----------------------------------------------------------
    private UserRegistry()
    {
        reload();
    }


    // ~ Methods ...............................................................

    // ----------------------------------------------------------
    public static UserRegistry getInstance()
    {
        if ( instance == null )
        {
            instance = new UserRegistry();
        }

        return instance;
    }


    // ----------------------------------------------------------
    public synchronized void createUser(
        String username,
        String password,
        CSPath workspaceLoc,
        String authenticator )
    {
        if ( userMap.containsKey( username ) )
        {
            return;
        }

        ArrayList<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
        roles.add( GroupFactory.DEFAULT_ROLE );

        String passwordHash = MD5.getHashString( password );

        CloudSpaceUser user = new CloudSpaceUser( username,
            passwordHash,
            true,
            authenticator );
        if ( workspaceLoc != null )
        {
            File workDir = new File( workspaceLoc.getPhysicalFile(), username );
            File snapDir = CloudSpaceConfiguration.getInstance()
                .getFileInStorage( "snapshot/" + username, true );
            workDir.mkdir();
            snapDir.mkdir();
            PermissionsManager.getInstance()
                .addDirectoryPermission( new CSPath( workspaceLoc, username ),
                    new UserPermissionToken( username, "rw" ) );
            PermissionsManager.getInstance()
                .addDirectoryPermission( new CSPath( "snapshot:/" + username ),
                    new UserPermissionToken( username, "r" ) );
        }
        userMap.put( username, user );

        updateConfigFile();
    }


    // ----------------------------------------------------------
    public synchronized void deleteUser( String username )
    {
        if ( userMap.containsKey( username ) )
        {
            userMap.remove( username );

            updateConfigFile();
        }
        else
        {
            log.warn( "Attempted to delete user '" + username + "' who "
                + "does not exist; ignoring." );
        }
    }


    // ----------------------------------------------------------
    public synchronized CloudSpaceUser getUser( String username )
    {
        reload();
        return userMap.get( username );
    }


    public void changePassword( String username, String newPassword )
    {
        CloudSpaceUser user = userMap.get( username );

        CloudSpaceUser newUser = new CloudSpaceUser( username,
            MD5.getHashString( newPassword ),
            user.isEnabled(),
            user.getAuthenticator() );
        userMap.put( username, newUser );
        updateConfigFile();
        log.info( "Changed password for " + username );
    }


    public void setEnabled( String username, boolean changeStatus )
    {
        CloudSpaceUser user = userMap.get( username );
        CloudSpaceUser newUser = new CloudSpaceUser( user.getUsername(),
            "",
            changeStatus,
            user.getAuthenticator() );
        userMap.put( username, newUser );
        updateConfigFile();
        log.info( "Changed enable status of " + username + " to "
            + changeStatus );
    }


    public void changeAuthType( String username, String authType )
    {
        CloudSpaceUser user = userMap.get( username );
        if ( !user.getAuthenticator().equals( authType ) )
        {
            CloudSpaceUser newUser = new CloudSpaceUser( user.getUsername(),
                "",
                false,
                authType );
            userMap.put( username, newUser );
            updateConfigFile();
            log.info( "Changed auth type of " + username + " to " + authType );
        }
        else
        {
            log.info( "User " + username + " is already authtype " + authType );
        }
    }


    // -----------------------------------------------------------
    public boolean hasActiveUsers()
    {
        reload();
        // TODO: Needs to check for an admin account!
        return userMap.size() != 0;
    }


    // ----------------------------------------------------------
    public synchronized CloudSpaceUser userByAddingPassword(
        CloudSpaceUser user,
        String password )
    {
        if ( user.getPassword().length() > 0 )
        {
            return user;
        }
        else
        {
            CloudSpaceUser newUser = new CloudSpaceUser( user.getUsername(),
                password,
                user.isEnabled(),
                user.getAuthenticator() );

            userMap.put( user.getUsername(), newUser );
            return newUser;
        }
    }


    // ----------------------------------------------------------
    private synchronized void updateConfigFile()
    {
        Properties prop = new Properties();

        for ( String username : userMap.keySet() )
        {
            CloudSpaceUser user = (CloudSpaceUser)userMap.get( username );

            String password;
            if ( AUTHENTICATOR_LOCAL.equals( user.getAuthenticator() ) )
            {
                password = user.getPassword();
            }
            else
            {
                password = "";
            }

            String value = String.format( "%s,%s,%s",
                user.getAuthenticator(),
                password,
                user.isEnabled() ? "enabled" : "disabled" );

            prop.setProperty( username, value );
        }
        FileWriter writer = null;
        try
        {
            CloudSpaceConfiguration config = CloudSpaceConfiguration.getInstance();
            File configFile = config.getFileInStorage( USER_CONFIG_FILE, true );
            writer = new FileWriter( configFile );
            prop.store( writer, null );
        }
        catch ( IOException e )
        {
            log.error( "An exception occurred while writing the user.conf "
                + "file", e );
        }
        finally
        {
            if ( writer != null )
            {
                try
                {
                    writer.close();
                }
                catch ( Exception e )
                {
                    // Meh
                }
            }
        }
    }


    // ----------------------------------------------------------
    public synchronized List<String> getUserList()
    {
        reload();
        ArrayList<String> usernames = new ArrayList<String>( userMap.keySet() );
        Collections.sort( usernames );
        return usernames;
    }


    // ----------------------------------------------------------
    public synchronized void reload()
    {
        CloudSpaceConfiguration config = CloudSpaceConfiguration.getInstance();
        File configFile = config.getFileInStorage( USER_CONFIG_FILE );
        if ( configFile.exists() && configFile.lastModified() <= lastChecked )
        {
            return;
        }
        lastChecked = System.currentTimeMillis();
        userMap = new HashMap<String, CloudSpaceUser>();

        ArrayList<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
        roles.add( GroupFactory.DEFAULT_ROLE );

        FileReader reader = null;
        try
        {
            Properties prop = new Properties();
            reader = new FileReader( configFile );
            prop.load( reader );

            for ( Object username_ : prop.keySet() )
            {
                String username = (String)username_;
                String[] parts = prop.getProperty( username ).split( "," );

                String authenticator = parts[0];
                String passwordHash = parts[1];
                boolean enabled = "enabled".equals( parts[2] );

                CloudSpaceUser user = new CloudSpaceUser( username,
                    passwordHash,
                    enabled,
                    authenticator );

                userMap.put( username, user );
            }
        }
        catch ( IOException e )
        {
            // Do nothing. Having no user.conf file is acceptable when the
            // system is running in single-user mode.
        }
        finally
        {
            if ( reader != null )
            {
                try
                {
                    reader.close();
                }
                catch ( Exception e )
                {
                    // closed enough
                }
            }
        }
    }

}
